contents

Pair라는 개념은 매우 흔하지만, 제네릭 Pair 클래스는 자바의 표준 라이브러리(JDK)에 포함되어 있지 않습니다. 이는 종종 개발자들을 놀라게 하는 중요한 사실입니다. 대신, Pair는 개발자가 직접 만들거나 서드파티 라이브러리를 통해 사용하는, 두 개의 객체를 함께 담는 간단한 자료구조입니다.

Pair의 주된 목적은 자바에서 기본적으로 불가능한, 메서드에서 두 개의 값을 반환하는 편리한 방법을 제공하는 것입니다.


Pair 클래스가 해결하는 문제

자바 메서드는 단 하나의 값 또는 객체만 반환할 수 있습니다. 만약 한 번에 두 가지를 반환해야 한다면 어떻게 해야 할까요? 예를 들어, 배열에서 최솟값과 최댓값을 모두 찾는 메서드가 필요할 수 있습니다.

Pair 패턴 이전에는 개발자들이 다음과 같이 이상적이지 않은 해결책을 사용했습니다.

Pair 클래스는 제네릭을 사용하여 재사용 가능한 해결책을 제공합니다. 이는 간단한 두 요소 컨테이너 역할을 하여, 매번 특정 클래스를 새로 만들지 않고도 어떤 두 객체든 단일 단위로 묶어 반환할 수 있게 해줍니다.


나만의 Pair 클래스 만들기 📦

표준 라이브러리에 없기 때문에, 개발자나 프로젝트가 자신만의 Pair 구현을 갖는 것은 매우 일반적입니다. 좋은 최신 Pair 클래스는 제네릭을 사용하며 불변(immutable)입니다.

다음은 잘 설계된 완전한 예제입니다.

import java.util.Objects;

// 왼쪽(L)과 오른쪽(R) 타입에 제네릭을 사용합니다.
public final class Pair {

    private final L left;
    private final R right;

    // 정적 팩토리 메서드를 통한 생성을 강제하기 위해 private 생성자 사용
    private Pair(L left, R right) {
        this.left = left;
        this.right = right;
    }

    // 편리한 생성을 위한 정적 팩토리 메서드
    public static  Pair of(L left, R right) {
        return new Pair<>(left, right);
    }

    // 값에 대한 getter
    public L getLeft() {
        return left;
    }

    public R getRight() {
        return right;
    }

    // 컬렉션에서 올바르게 동작하도록 equals()와 hashCode() 재정의
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Pair pair = (Pair) o;
        return Objects.equals(left, pair.left) && Objects.equals(right, pair.right);
    }

    @Override
    public int hashCode() {
        return Objects.hash(left, right);
    }

    // 쉬운 디버깅을 위해 toString() 재정의
    @Override
    public String toString() {
        return "Pair{" +
                "left=" + left +
                ", right=" + right +
                '}';
    }
}

Pair 클래스 사용 방법

위 클래스를 사용하여, 이제 최솟값/최댓값 문제를 깔끔하게 해결할 수 있습니다.

public class ArrayUtils {

    public static Pair findMinMax(int[] numbers) {
        if (numbers == null || numbers.length == 0) {
            throw new IllegalArgumentException("배열은 비어 있을 수 없습니다.");
        }
        
        int min = numbers[0];
        int max = numbers[0];
        
        for (int number : numbers) {
            if (number < min) min = number;
            if (number > max) max = number;
        }
        
        // 두 개의 값을 하나의 Pair 객체로 감싸서 반환
        return Pair.of(min, max);
    }

    public static void main(String[] args) {
        int[] data = {5, 1, 9, 3, 7, 2, 8};
        
        Pair result = findMinMax(data);
        
        System.out.println("최솟값: " + result.getLeft());   // 출력: 최솟값: 1
        System.out.println("최댓값: " + result.getRight());  // 출력: 최댓값: 9
    }
}

Pair 클래스를 찾을 수 있는 곳

직접 작성하고 싶지 않다면, 몇몇 인기 있는 라이브러리에서 자체 구현을 제공합니다.


현대적인 대안: 레코드 (자바 16 이상) ✨

많은 경우, "두 개의 값을 반환"하는 문제를 해결하는 현대적인 방법은 **레코드(record)**를 사용하는 것입니다. 레코드는 불변의 데이터 전달 클래스를 만들기 위한 간결한 문법입니다.

제네릭 Pair를 만드는 대신, 거의 상용구 코드 없이 의미가 명확한 특정 레코드를 만들 수 있습니다.

최솟값/최댓값 예제에 레코드 사용:

// 단 한 줄로 레코드를 정의!
public record MinMaxResult(int min, int max) {}

이 한 줄은 자동으로 다음과 같은 것들을 갖춘 클래스를 생성합니다.

사용법:

public static MinMaxResult findMinMaxWithRecord(int[] numbers) {
    // ... 이전과 동일한 로직 ...
    return new MinMaxResult(min, max);
}

// --- main 메서드 내 ---
MinMaxResult result = findMinMaxWithRecord(data);
System.out.println("최솟값: " + result.min());
System.out.println("최댓값: " + result.max());

제네릭 Pair 클래스도 여전히 유용한 도구이지만, 반환하는 두 값이 명확하고 구체적인 의미를 갖는다면, 레코드가 종종 더 깔끔하고, 표현력이 좋으며, 현대적인 자바 접근 방식입니다.

references